Game = function (layer, width, height) {
	let Length = width * height;
	this.Map = new Array(Length);
	this.Letters = new Array(Length);
	this.ButtonEvents = new Array(Length);
	this.AlreadyOpened = new Array(Length);
	this.Rules = [];
	this.Events = {};
	window.Bundle = {};
	for (let i = 0; i < Length; i++) {
		this.Map[i] = 0;
		this.Letters[i] = null;
		this.ButtonEvents[i] = null;
		this.AlreadyOpened[i] = false;
	}
	this.Sprites = new Array(Length);
	this.width = width;
	this.height = height;
	this.setPlayerPosition(0, 0);
	this.Layer = layer;
	//Fill Layer With Sprite
	let Size = Math.min(C.GameWidth / width, C.GameHeight / height);
	let XOffset = (C.GameWidth - Size * width) / 2;
	let YOffset = (C.GameHeight - Size * height) / 2;
	let SingleBlock;
	let Index = 0;
	let Scale = Size / C.BlockSize;
	for (let j = 1; j <= height; j++)
		for (let i = 0; i < width; i++) {
			this.Sprites[Index++] = (SingleBlock = cc.Sprite.create());
			SingleBlock.userData = -1;
			SingleBlock.setAnchorPoint(0, 0);
			SingleBlock.setPosition(i * Size + XOffset, C.GameHeight - j * Size - YOffset);
			SingleBlock.setScale(Scale);
			this.Layer.addChild(SingleBlock, 1);
		}
	this.PlayerSprite = cc.Sprite.create(R.Player);
	this.PlayerSprite.setScale(Scale);
	this.PlayerSprite.setAnchorPoint(0, 0);
	this.PlayerSprite.setPosition(0, 0);
	this.Layer.addChild(this.PlayerSprite, 1);
	this.Scale = Scale;
	this.onWin = null;
};
Game.prototype.setPlayerPosition = function (x, y) {
	if (x < 1 || y < 1 || x > this.width || y > this.height) return false;
	this.playerX = x;
	this.playerY = y;
	this.playerPos = this.getIndex(x, y);
	return true;
};
Game.prototype.openButton = function (ind) {
	if (!this.ButtonEvents[ind]) return;
	if (!this.AlreadyOpened[ind]) {
		this.AlreadyOpened[ind] = true;
		this.ButtonEvents[ind](true, this);
	}
};
Game.prototype.closeButton = function (ind) {
	if (!this.ButtonEvents[ind]) return;
	if (this.AlreadyOpened[ind]) {
		this.AlreadyOpened[ind] = false;
		this.ButtonEvents[ind](false, this);
	}
};
Game.prototype.draw = function () {
	let LetterSprite;
	for (let i = 0; i < this.Sprites.length; i++) {
		this.Sprites[i].setSpriteFrame(BlockImgs[this.Map[i]]);
		if (i === this.playerPos)
			this.PlayerSprite.setPosition(this.Sprites[i].getPosition());
		if (LetterSprite = this.Letters[i])
			LetterSprite.setPosition(this.Sprites[i].getPosition());
	}
};
Game.prototype.getIndex = function (x, y) {
	return (y - 1) * this.width + x - 1;
};
Game.prototype.getTile = function (x, y) {
	return this.Map[this.getIndex(x, y)];
};
Game.prototype.setTile = function (x, y, id) {
	this.Map[this.getIndex(x, y)] = id;
};
Game.prototype.setMap = function (data) {
	this.Map = data;
};
Game.prototype.destroy = function () {
	for (let i = 0; i < this.Sprites.length; i++) this.Layer.removeChild(this.Sprites[i]);
	this.Layer.removeChild(this.PlayerSprite);
	for (let i = 0; i < this.Letters.length; i++) this.Layer.removeChild(this.Letters[i]);
	for (let i = 0; i < this.Rules.length; i++) this.Rules[i].unapply();
};
Game.prototype.removeLetter = function (x, y) {
	let q = this.getIndex(x, y);
	if (!this.Letters[q]) return;
	for (let i = 0; i < this.Rules.length; i++)
		if (this.Rules[i].contains(q)) {
			this.Rules[i].unapply();
			this.Rules.splice(i, 1);
			break;
		}
	let tmp = this.Letters[q];
	this.Letters[q] = null;
	return tmp;
};
Game.prototype.setLetter = function (x, y, ind, spr) {
	let q = this.getIndex(x, y);
	if (spr) this.Letters[q] = spr; else {
		this.Letters[q] = cc.Sprite.createWithSpriteFrame(LetterImgs[ind]);
		this.Letters[q].setScale(this.Scale);
		this.Letters[q].setAnchorPoint(0, 0);
		this.Letters[q].userData = ind;
		this.Layer.addChild(this.Letters[q], 1);
	}
	switch (LetterTypes[ind]) {
		case 0: {
			if (x + 2 > this.width) break;
			let tmp;
			let type;
			if ((tmp = this.Letters[q + 1]) && LetterTypes[tmp.userData] === 1 && (tmp = this.Letters[q + 2]) && LetterTypes[type = tmp.userData] === 2)
				this.Rules.push(new Rule(ind, type, q).apply());
			break;
		}
		case 1: {
			if (x + 1 > this.width || x < 2) break;
			let tmp;
			let type1, type2;
			if ((tmp = this.Letters[q - 1]) && LetterTypes[type1 = tmp.userData] === 0 && (tmp = this.Letters[q + 1]) && LetterTypes[type2 = tmp.userData] === 2)
				this.Rules.push(new Rule(type1, type2, q - 1).apply());
			break;
		}
		case 2: {
			if (x < 3) break;
			let tmp;
			let type;
			if ((tmp = this.Letters[q - 2]) && LetterTypes[type = tmp.userData] === 0 && (tmp = this.Letters[q - 1]) && LetterTypes[tmp.userData] === 1)
				this.Rules.push(new Rule(type, ind, q - 2).apply());
			break;
		}
	}
};
Game.prototype.registerButtonEvent = function (x, y, event) {
	this.ButtonEvents[this.getIndex(x, y)] = event;
};
Game.prototype.callEvent = function (n) {
	if (n in this.Events)
		this.Events[n](this);
};
Game.prototype.processDirection = function (d) {
	let mx = [-1, 0, 1, 0][d], my = [0, -1, 0, 1][d];
	let nx = this.playerX + mx, ny = this.playerY + my;
	if (nx < 1 || nx > this.width || ny < 1 || ny > this.height) return;
	let ind = this.getIndex(nx, ny);
	let bid = this.Map[ind];
	if (isBlock(bid, "pushable")) {
		let xx = nx + mx, yy = ny + my;
		if (xx < 1 || xx > this.width || yy < 1 || yy > this.height) return;
		let indd = this.getIndex(xx, yy);
		let Tile = this.Map[indd];
		if (!isBlock(Tile, "forbid") && !isBlock(Tile, "pushable")) {
			let Origin = this.playerPos;
			let spr = this.Sprites[ind];
			this.setTile(nx, ny, (spr.userData === -1 ? 0 : spr.userData));
			spr.userData = -1;
			this.Sprites[indd].userData = Tile;
			this.setPlayerPosition(nx, ny);
			this.callEvent("onMove");
			this.closeButton(Origin);
			indd = this.getIndex(xx, yy);
			this.Map[indd] = bid;
			this.openButton(indd);
		}
	} else if (!isBlock(bid, "forbid")) {
		if (this.Letters[ind]) {
			let xx = nx, yy = ny;
			let indd = ind;
			while (this.Letters[indd]) {
				xx += mx;
				yy += my;
				if (xx === 0 || xx === this.width + 1 || yy === 0 || yy === this.height + 1) return;
				indd = this.getIndex(xx, yy);
				if (isBlock(this.Map[indd], "forbid") || isBlock(this.Map[indd], "pushable")) return;
			}
			while (xx !== nx || yy !== ny) {
				let Sprite = this.removeLetter(xx - mx, yy - my);
				this.setLetter(xx, yy, Sprite.userData, Sprite);
				xx -= mx;
				yy -= my;
			}
			let Origin = this.playerPos;
			this.setPlayerPosition(nx, ny);
			this.callEvent("onMove");
			if (this.Map[Origin] === 4) {
				console.log("Close");
				this.closeButton(Origin);
			}
			this.openButton(this.playerPos);
		} else {
			if (isBlock(bid, "win") && this.onWin) this.onWin();
			let Origin = this.playerPos;
			this.setPlayerPosition(nx, ny);
			this.callEvent("onMove");
			if (this.Map[Origin] === 4) {
				console.log("Close");
				this.closeButton(Origin);
			}
			this.openButton(this.playerPos);
		}
	}
	//console.log("Rules:", this.Rules.length);
	this.draw();
};
Game.prototype.onResize = function () {
	let Size = Math.min(C.GameWidth / this.width, C.GameHeight / this.height);
	let XOffset = (C.GameWidth - Size * this.width) / 2;
	let YOffset = (C.GameHeight - Size * this.height) / 2;
	let SingleBlock;
	let Index = 0;
	let Scale = Size / C.BlockSize;
	for (let j = 1; j <= this.height; j++)
		for (let i = 0; i < this.width; i++) {
			SingleBlock = this.Sprites[Index++];
			SingleBlock.setPosition(i * Size + XOffset, C.GameHeight - j * Size - YOffset);
			SingleBlock.setScale(Scale);
		}
	for (let i = 0; i < this.Letters.length; i++) if (SingleBlock = this.Letters[i]) SingleBlock.setScale(Scale);
	this.PlayerSprite.setScale(Scale);
	this.draw();
};
Game.prototype.observeEvent = function (name, func) {
	Object.defineProperty(this.Events, name, {
		value: func
	});
};
LoadGame = function (layer, obj) {
	let config = obj.config;
	let g = new Game(layer, config[0], config[1]);
	g.setMap(obj.data.slice(0));
	g.setPlayerPosition(config[2], config[3]);
	if ("letters" in obj) {
		let Letters = obj.letters;
		for (let i = 0; i < Letters.length; i += 3)
			g.setLetter(Letters[i], Letters[i + 1], Letters[i + 2]);
	}
	if ("buttons" in obj) {
		let Buttons = obj.buttons;
		for (let i = 0; i < Buttons.length; i += 3)
			g.registerButtonEvent(Buttons[i], Buttons[i + 1], Buttons[i + 2]);
	}
	if ("events" in obj) {
		let Events = obj.events;
		for (let i = 0; i < Events.length; i += 2)
			g.observeEvent(Events[i], Events[i + 1]);
	}
	if ("onStart" in g.Events)
		g.Events.onStart(g);
	return g;
};